/**
 * This file is part of git-as-svn. It is subject to the license terms
 * in the LICENSE file found in the top-level directory of this distribution
 * and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
 * including this file, may be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file.
 */
package svnserver.repository.mapping;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.*;
import org.mapdb.DB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import svnserver.StringHelper;
import svnserver.repository.RepoContainer;
import svnserver.repository.VcsRepository;
import svnserver.repository.VcsRepositoryMapping;
import svnserver.repository.git.GitPushMode;
import svnserver.repository.git.GitRepository;
import svnserver.repository.locks.PersistentLockFactory;
import svnserver.service.RepositoryUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
 * Simple repository mapping by predefined list.
 *
 * @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
 */
public class RepositoryListMapping implements VcsRepositoryMapping {
  @NotNull
  private final NavigableMap<String, VcsRepository> mapping;
  private static final Logger log = LoggerFactory.getLogger(RepositoryListMapping.class);

  public RepositoryListMapping(@NotNull Map<String, VcsRepository> mapping) {
    this.mapping = new ConcurrentSkipListMap<>(mapping);
  }

  @Nullable
  public RepoContainer makeBundle(@NotNull SVNURL baseURL, @NotNull String mkey, @NotNull DB cacheDb,
      @NotNull FileRepository repo, @NotNull String branch) throws SVNException {
    try {
      RepoContainer container = new RepoContainer(baseURL, new GitRepository(repo, Collections.emptyList(),
          GitPushMode.SIMPLE, branch, true, new PersistentLockFactory(cacheDb), cacheDb), mkey);
      log.info("create new repo cache, url: {} ", baseURL);
      mapping.put(mkey, container.getRepository());
      return container;
    } catch (IOException e) {
      return null;
    }
  }

  @Nullable
  public RepoContainer getRepoContainer(@NotNull SVNURL baseURL, @NotNull DB cacheDb, @NotNull String realPath,
      @NotNull String branch) throws SVNException {
    if (mapping.size() >= 500) {
      /// TO remove first entry
      log.debug("current repository size {}, poll first entry", mapping.size());
      mapping.pollFirstEntry();
    }
    FileRepository filerepo = null;
    String mkey;
    String rbranch;
    try {
      filerepo = new FileRepository(realPath);
      final Ref ref = filerepo.findRef(branch);
      if (ref == null) {
        filerepo.close();
        throw new SVNException(
            SVNErrorMessage.create(SVNErrorCode.RA_DAV_PATH_NOT_FOUND, "branch " + branch + " not exists."));
      }
      rbranch = ref.getTarget().getName().substring("refs/heads/".length());
      mkey = realPath + "@" + rbranch;
    } catch (IOException e) {
      log.error("FileRepository not found: ", e);
      return null;
    }
    GitRepository entry = (GitRepository) mapping.get(mkey);
    if (entry == null) {
      return makeBundle(baseURL, mkey, cacheDb, filerepo, rbranch);
    }
    /// entry
    String uuid = RepositoryUtils.GetUUID(realPath.toString());
    if (uuid != null) {
      if (entry.getUuid().equals(uuid)) {
        filerepo.close();
        log.info("Find {} in mapdb, svn.uuid: {}", mkey, uuid);
        return new RepoContainer(baseURL, entry, mkey);
      }
    }
    mapping.remove(mkey);
    return makeBundle(baseURL, mkey, cacheDb, filerepo, rbranch);
  }

  public void EraseRepoContainer(@NotNull RepoContainer rc) {
    // Remove invaild cache
    VcsRepository repo = mapping.remove(rc.MapKey());
    if (repo != null) {
      log.error("Erase {} from mapping", rc.MapKey());
    }
  }

  @Nullable
  public static <T> Map.Entry<String, T> getMapped(@NotNull NavigableMap<String, T> mapping, @NotNull String fullPath) {
    final Map.Entry<String, T> entry = mapping.floorEntry(StringHelper.normalize(fullPath));
    if (entry != null) {
      return entry;
    }
    return null;
  }

  public static RepositoryListMapping create(@NotNull String prefix, @NotNull VcsRepository repository) {
    return new Builder().add(prefix, repository).build();
  }

  public static class Builder {
    @NotNull
    private final Map<String, VcsRepository> mapping = new ConcurrentSkipListMap<>();

    public Builder add(@NotNull String prefix, @NotNull VcsRepository repository) {
      mapping.put(StringHelper.normalize(prefix), repository);
      return this;
    }

    public RepositoryListMapping build() {
      return new RepositoryListMapping(mapping);
    }
  }
}
